<?php
// +----------------------------------------------------------------------
// | Bwsaas
// +----------------------------------------------------------------------
// | Copyright (c) 2015~2020 http://www.buwangyun.com All rights reserved.
// +----------------------------------------------------------------------
// | Licensed ( http://www.apache.org/licenses/LICENSE-2.0 )
// +----------------------------------------------------------------------
// | Gitee ( https://gitee.com/buwangyun/bwsaas )
// +----------------------------------------------------------------------
// | Author: buwangyun <hnlg666@163.com>
// +----------------------------------------------------------------------
// | Date: 2020-9-28 10:55:00
// +----------------------------------------------------------------------

namespace buwang\service;

use app\common\model\Cache as CacheModel;
use app\common\model\member\Wallet;
use app\common\model\MemberPluginOrder;
use app\common\model\Miniapp;
use app\common\model\MiniappModule;
use app\manage\model\admin\Plugin;
use app\manage\model\AuthGroup;
use app\manage\model\AuthGroupAccess;
use app\manage\model\AuthGroupNode;
use app\manage\model\AuthNode;
use RecursiveDirectoryIterator;
use RecursiveIteratorIterator;
use think\Exception;
use think\facade\Cache;
use think\facade\Db;
use buwang\util\File;
use buwang\util\Sql;
use ZipArchive;
use buwang\traits\ErrorTrait;
use app\manage\model\Member;
use app\manage\model\Config;
use app\manage\model\ConfigTab;
use app\manage\model\ConfigGroup;
use app\manage\model\ConfigGroupData;

/**
 * 插件服务类
 * Class PluginService
 * @package buwang\service
 */
class PluginService
{

    use ErrorTrait;

    protected static $name;//插件目录名

    /**
     * 安装插件.
     *
     * @param string $name 插件名称
     * @param bool $force 是否覆盖
     * @param array $extend 扩展参数
     *
     * @return bool
     * @throws Exception
     *
     */
    public static function install($name, $force = false, $extend = [])
    {
        define('ADDONS_DIR', $name);//定义一个常量表示插件目录名
        self::$name = $name;
        try {
            // 检查插件是否完整
            self::check($name);
            if (!$force) {
                //检查有没有冲突的文件
                self::noconflict($name);
            }
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }

        Db::startTrans();
        try {
            $info = get_addons_info($name);

            //TODO 引入角色组和节点
            self::createMenu($name, $info);
            // 默认启用该插件
            if (!$info['status']) {
                $info['status'] = 1;
                set_addons_info($name, $info);
            }

            // 执行安装脚本
            $class = get_addons_class($name);
            if (class_exists($class)) {
                $addon = new $class(app());
                $addon->install();

                //TODO 设置缓存 若addons/demoPlugin/Plugin.php存在$cache数组
                /*if (isset($addon->cache) && is_array($addon->cache)) {
                    self::installAddonCache($addon->cache, $name);
                }*/
            }
            self::runSQL($name);
            //插件表中增加记录
            $info['logo_image'] = '/static/addons/' . strtolower($name) . '/logo.png';//插件logos
            $info['subtitle'] = $info['description'] ?? '';
            Plugin::create($info);

            //把插件目录下的app和public目录复制至对应目录下 addons/demoPlugin/app => app
            //201130 app和public目录移至addons/demoPlugin/install目录下
            foreach (self::getCheckDirs() as $k => $dir) {
                if (is_dir(ADDON_PATH . $name . DS . 'install' . DS . $dir)) {
                    File::copy_dir(ADDON_PATH . $name . DS . 'install' . DS . $dir, app()->getRootPath() . $dir);
                }
            }
            //拷贝静态资源文件 addons/demoPlugin/static/ => public/static/addons/demoPlugin/
            //201130 静态资源文件目录移至addons/demoPlugin/install/static/目录下
            if (file_exists(ADDON_PATH . $name . DS . 'install' . DS . "static" . DS)) {
                File::copy_dir(ADDON_PATH . $name . DS . 'install' . DS . "static" . DS, root_path() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS);
            }
            //TODO 2021/5/11 安装插件配置
            self::installConfigAndGroupData($name, $info);
            Db::commit();
        } catch (Exception $e) {
            Db::rollback();
            throw new Exception($e->getMessage());
        }
        // 刷新
        self::refresh();
        return true;
    }

    /**
     * 卸载插件.
     *
     * @param string $name
     * @param bool $force 是否强制卸载
     *
     * @return bool
     * @throws Exception
     *
     */
    public static function uninstall($name, $force = false)
    {
        //监测插件目录是否存在
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('插件不存在');
        }
        define('ADDONS_DIR', $name);//定义一个常量表示插件目录名
        //set插件名称
        self::$name = $name;
        //查找插件
        $plugin = Plugin::where('name', self::$name)->find();
        if (!$plugin) throw new Exception('插件尚未安装');
        //插件已被购买就无法卸载
        $plugin_order = MemberPluginOrder::where('plugin_id', $plugin['id'])->find();
        if (!$force && $plugin_order) throw new Exception('插件已被购买,无法卸载');
        // 执行卸载脚本
        Db::startTrans();
        try {
            // 获取插件的配置信息
            $info = get_addons_info($name);

            //删除插件节点 用户组 用户组节点 应用功能的记录
            self::deleteMenu();
            //如果插件未禁用则改成禁用（还原插件配置）
            if ($info['status']) {
                $info['status'] = 0;
                set_addons_info($name, $info);
            }
            //得到插件对应的 Plugin.php 类的全命名空间类名
            $class = get_addons_class($name);
            //判断该类是否存在
            if (class_exists($class)) {
                //如果存在则实例化
                $addon = new $class(app());
                //清除插件列表缓存
                Cache::has('addonslist') && Cache::delete('addonslist');
                //执行插件应用Plugin.php自定义的卸载方法
                $addon->uninstall();
            }

            //TODO 删除插件相关的表[开发的时候设计好规范的表结构]
            //查询数据表是否存在
            $table_name = '_' . $name;
            $table_temp = Db::query("SHOW TABLE STATUS LIKE '%{$table_name}%'");
            if (!empty($table_temp)) {
                foreach ($table_temp as $v) {
                    Db::execute("DROP TABLE IF EXISTS {$v['Name']}");
                }
            }
            //执行卸载sql
            self::runSQL($name, 'uninstall');

            //删除插件表中记录
            Plugin::where('name', $name)->delete();
            //删除插件的购买订单
            MemberPluginOrder::where('plugin_id', $plugin['id'])->delete();
            // 移除插件全局资源文件
            if ($force) {
                $list = self::getGlobalFiles($name);
                foreach ($list as $k => $v) {
                    @unlink(app()->getRootPath() . $v);
                }
            }
            //删除模块前台模板
            if (is_dir(TEMPLATE_PATH . 'addons' . DS . $name . DS)) {
                File::del_dir(TEMPLATE_PATH . 'addons' . DS . $name . DS);
            }
            //静态资源移除
            if (is_dir(root_path() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS)) {
                File::del_dir(root_path() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($name) . DS);
            }

            Db::commit();
        } catch (\Exception $e) {
            Db::rollback();
            throw new Exception($e->getMessage());
        }
        // 刷新
        self::refresh();
        return true;
    }

    /**
     * 启用.
     *
     * @param string $name 插件名称
     * @param bool $force 是否强制覆盖
     *
     * @return bool
     */
    public static function enable($name, $force = false)
    {
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('插件不存在！');
        }

        define('ADDONS_DIR', $name);//定义一个常量表示插件目录名
        self::$name = $name;

        $info = get_addons_info($name);

        //执行启用脚本
        try {
            self::enableMenu();

            //更新插件记录表记录状态为1
            Plugin::update(['status' => 1], ['name' => $name]);

            $class = get_addons_class($name);
            if (class_exists($class)) {
                $addon = new $class(app());
                if (method_exists($class, 'enabled')) {
                    $addon->enabled();
                }
            }

            $info['status'] = 1;
            unset($info['url']);
            set_addons_info($name, $info);
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }
        // 刷新
        self::refresh();

        return true;
    }

    /**
     * 禁用.
     *
     * @param string $name 插件名称
     * @param bool $force 是否强制禁用
     *
     * @return bool
     * @throws Exception
     *
     */
    public static function disable($name, $force = false)
    {
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('插件不存在！');
        }

        define('ADDONS_DIR', $name);//定义一个常量表示插件目录名
        self::$name = $name;

        $info = get_addons_info($name);
        // 执行禁用脚本
        try {
            self::disableMenu();

            //更新插件记录表记录状态为0
            Plugin::update(['status' => 0], ['name' => $name]);

            $class = get_addons_class($name);
            if (class_exists($class)) {
                $addon = new $class(app());

                if (method_exists($class, 'disabled')) {
                    $addon->disabled();
                }
            }

            $info['status'] = 0;
            unset($info['url']);
            set_addons_info($name, $info);
        } catch (Exception $e) {
            throw new Exception($e->getMessage());
        }

        // 刷新
        self::refresh();

        return true;
    }

    /**
     * 租户购买插件
     * @param int $user_id 顶级租户用户ID
     * @param Object $plugin
     * @return bool
     * @throws Exception
     */
    public static function buy($user_id, $plugin): bool
    {
        define('ADDONS_DIR', $plugin['name']);//定义一个常量表示插件目录名
        self::$name = $plugin['name'];

        list($scopes, $type) = explode('_', $plugin['type']);
        if (!in_array($scopes, ['member', 'all'])) throw new Exception('此插件不支持租户购买');//TODO 平台插件不可购买

        //检查是否有menu.php TODO: 2021/4/29修复BUG 没有插件菜单也可购买，不能算无法购买
        $path = root_path() . "addons" . DS . self::$name . DS . "install" . DS . "menu.php";
        if (!file_exists($path)) throw new Exception('插件缺少菜单配置文件menu.php'); //原逻辑：没有菜单
        $data = include $path;
//        if (empty($data)) return true; //原逻辑：没有菜单
        try {
            //NOTE 账户余额
            //如果价格<=0，就不在查询数据库
            if ($plugin['price'] > 0 && $plugin['price'] > Wallet::getMoney($user_id)) throw new Exception('余额不足');
            //NOTE 扣余额
            $plugin['price'] > 0 && Wallet::changeMoney($user_id, 'plugin', 0 - $plugin['price'], '购买插件' . self::$name);
            //如果插件存在菜单，则处理菜单逻辑
            if (!empty($data)) {
                $group_name_arr = [];//用户组唯一标识
                foreach ($data as $item) {
                    //租户购买时只给租户插入租户角色组
                    $item['scopes'] === 'member' && $group_name_arr[] = strpos($item['group_name'], self::$name) !== false ? $item['group_name'] : self::$name . '_' . $item['group_name'];
                }
                // TODO 2021/4/29 租户拥有的角色组类型为plugin ，租户角色的app_name = 租户标识
                $group_type = 'plugin';//角色类型
                $app_name = self::$name; //角色app标识
                //TODO 查询该插件安装后包含的功能权限 member+id=0
                $group_arrs = AuthGroup::where('group_name', 'IN', $group_name_arr)->where('type', $group_type)->where('app_name', $app_name)->where('member_id', 0)->field('id,name')->select();
                if (empty($group_arrs)) exception("插件安装数据有误！");

                //给租户添加该插件的角色组//TODO 系统插件【包括租户平台插件，总运营管理平台插件】
                $group_ids_arr = [];
                //TODO 给租户增加角色组[购买插件就是相当于购买了相应的角色组]
                foreach ($group_arrs as $group_arr) {
                    $groupAccessParam[] = [
                        'uid' => $user_id,
                        'group_id' => $group_arr['id'],
                        'name' => $group_arr['name'],
                        'scopes' => 'member'
                    ];
                    //拼接group_id
                    $group_ids_arr[] = $group_arr['id'];
                }

                if ($type === 'system') {
                    //TODO:购买插件角色删除再添加防止冲突
                    AuthGroupAccess::where('uid', '=', $user_id)->where('group_id', 'IN', $group_ids_arr)->where('scopes', '=', 'member')->delete();
                    //给租户分配角色
                    if (!empty($groupAccessParam)) {
                        $authGroupAccess = new AuthGroupAccess();
                        $authGroupAccess->saveAll($groupAccessParam);
                    }
                } else {//TODO 应用插件购买
                    //根据插件获取出租户的哪个应用购买的插件
                    $member_miniapp = Db::name('member_miniapp')->where('member_id', $user_id)->where('miniapp_id', '=', function ($query) use ($type) {
                        $query->name('miniapp')->where('dir', $type)->field('id');
                    })->find();
                    //执行后续购买逻辑
                    MiniappService::pluginBuy($user_id, $plugin, $group_ids_arr);
                }
            }
            //创建订单
            $pluginOrderParam = [
                'member_id' => $user_id,
                'plugin_id' => $plugin['id'],
                'member_miniapp_id' => $member_miniapp['id'] ?? 0,
                'price' => $plugin['price'],
            ];
            MemberPluginOrder::create($pluginOrderParam);
            //TODO: 初始化租户配置和组合数据（租户配置和）
            self::initMemberConfigAndData($user_id, $plugin['name'], $plugin);
        } catch (\Throwable $e) {
            throw new Exception($e->getMessage());
        }

        return true;
    }

    /**
     * 刷新插件缓存文件.
     *
     * @return bool
     * @throws Exception
     *
     */
    public static function refresh()
    {
        $file = app()->getRootPath() . 'config' . DS . 'addons.php';

        $config = get_addons_autoload_config(true);

        if ($config['autoload']) {
            return;
        }

        if (!File::is_really_writable($file)) {
            throw new Exception('addons.php文件没有写入权限');
        }

        if ($handle = fopen($file, 'w')) {
            fwrite($handle, "<?php\n\n" . 'return ' . var_export($config, true) . ';');
            fclose($handle);
        } else {
            throw new Exception('文件没有写入权限');
        }

        return true;
    }

    /**
     * 解压插件.
     *
     * @param string $name 插件名称
     *
     * @return string
     * @throws Exception
     *
     */
    public static function unzip($name)
    {
        $file = app()->getRootPath() . 'runtime' . DS . 'addons' . DS . $name . '.zip';
        $dir = ADDON_PATH . $name . DS;
        if (class_exists('ZipArchive')) {
            $zip = new ZipArchive();
            if ($zip->open($file) !== true) {
                throw new Exception('Unable to open the zip file');
            }
            if (!$zip->extractTo($dir)) {
                $zip->close();

                throw new Exception('Unable to extract the file');
            }
            $zip->close();

            return $dir;
        }

        throw new Exception('无法执行解压操作，请确保ZipArchive安装正确');
    }

    /**
     * 注册插件缓存
     * @return boolean
     */
    public static function installAddonCache(array $cache, $name)
    {
        $data = array();
        foreach ($cache as $key => $rs) {
            $add = array(
                'key' => $key,
                'name' => $rs['name'],
                'module' => isset($rs['module']) ? $rs['module'] : $name,
                'model' => $rs['model'],
                'action' => $rs['action'],
                //'param' => isset($rs['param']) ? $rs['param'] : '',
                'system' => 0,
            );
            CacheModel::create($add);
        }
        return true;
    }

    /**
     * 执行数据库脚本
     * @param string $name
     * @param string $Dir
     * @return bool
     * @throws Exception
     */
    public static function runSQL(string $name = '', string $Dir = 'install')
    {
        //数据库安装脚本文件地址 addons/demoPlugin/install/install.sql
        //数据库卸载脚本文件地址 addons/demoPlugin/uninstall/uninstall.sql
        $sql_file = ADDON_PATH . "{$name}" . DS . "{$Dir}" . DS . "{$Dir}.sql";
        if (file_exists($sql_file)) {
            $table_prefix = config('database.connections.mysql.prefix');
            $sql_statement = Sql::getSqlFromFile($sql_file,false,[],$table_prefix);
            if (!empty($sql_statement)) {
//                //TODO 修改表前缀
//                $table_prefix = config('database.connections.mysql.prefix');
//                if (!empty($table_prefix)) {
//                    $sql_statement = str_replace('bw_', $table_prefix, $sql_statement);
//                    $sql_statement = str_replace('__BWPREFIX__', $table_prefix, $sql_statement);
//                }

                foreach ($sql_statement as $value) {
                    try {
                        Db::execute($value);
                    } catch (\Exception $e) {
                        throw new Exception("导入SQL失败，请检查{$name}应用的sql的语句是否正确");
                    }
                }
            }
        }
        return true;
    }

    /**
     * 是否有冲突
     *
     * @param string $name 插件名称
     * @return  boolean
     * @throws  AddonException
     */
    public static function noconflict($name)
    {
        // 检测冲突文件
        $list = self::getGlobalFiles($name, true);
        if ($list) {
            //发现冲突文件，抛出异常
            throw new Exception("发现冲突文件:" . json_encode($list));
        }
        return true;
    }

    /**
     * 获取插件在全局的文件
     *
     * @param string $name 插件名称
     * @return  array
     */
    public static function getGlobalFiles($name, $onlyconflict = false)
    {
        $list = [];
        $addonDir = ADDON_PATH . $name . DS;
        // 扫描插件目录是否有覆盖的文件
        foreach (self::getCheckDirs() as $k => $dir) {
            $checkDir = root_path() . $dir . DS;
            if (!is_dir($checkDir)) {
                continue;
            }

            //检测到存在插件外目录
            if (is_dir($addonDir . $dir)) {
                //匹配出所有的文件
                $files = new RecursiveIteratorIterator(
                    new RecursiveDirectoryIterator($addonDir . $dir, RecursiveDirectoryIterator::SKIP_DOTS), RecursiveIteratorIterator::CHILD_FIRST
                );

                foreach ($files as $fileinfo) {
                    if ($fileinfo->isFile()) {
                        $filePath = $fileinfo->getPathName();
                        $path = str_replace($addonDir, '', $filePath);
                        if ($onlyconflict) {
                            $destPath = app()->getRootPath() . $path;
                            if (is_file($destPath)) {
                                if (filesize($filePath) != filesize($destPath) || md5_file($filePath) != md5_file($destPath)) {
                                    $list[] = $path;
                                }
                            }
                        } else {
                            $list[] = $path;
                        }
                    }
                }
            }
        }
        return $list;
    }

    /**
     * 获取检测的全局文件夹目录
     * @return  array
     */
    protected static function getCheckDirs()
    {
        return [
            'app',
        ];
    }

    /**
     * 检测插件是否完整.
     *
     * @param string $name 插件名称
     *
     * @return bool
     * @throws Exception
     *
     */
    public static function check($name)
    {
        if (!$name || !is_dir(ADDON_PATH . $name)) {
            throw new Exception('插件不存在！');
        }
        $addonClass = get_addons_class($name);
        if (!$addonClass) {
            throw new Exception('插件主启动程序文件Plugin.php不存在');
        }
        $addon = new $addonClass(app());
        if (!$addon->checkInfo()) {
            throw new Exception('配置文件不完整，必须配置项：' . implode(",", ADDON_INFO_TYPE));
        }
        return true;
    }

    /**
     * 导入角色数据
     * @param string $name 插件标识
     * @param array $info 插件信息
     * @return bool
     * @throws \Exception
     */
    private static function createMenu(string $name, array $info)
    {
        //检查是否有menu.php
        $path = root_path() . "/addons/{$name}/install/menu.php";
        if (!file_exists($path)) return true;
        $data = include $path;
        if (empty($data)) return true;

        //校验type是否合法
        list($scopes, $type) = explode('_', $info['type']);
        if (!in_array($scopes, ['admin', 'member', 'all'])) throw new Exception('插件类型一有误,应为admin或member或all');
        if (in_array($scopes, ['admin', 'all']) && $type !== 'system') throw new Exception('平台插件类型二有误,应为system');
        //TODO 2021/4/29 更改插件角色类型为插件类型
        $group_type = 'plugin';
        //TODO 2021/4/29 更改角色app_name为插件名称
        $app_name = $name;
        $is_app = false;//是否是应用
        //应用里面的插件判断
        if ($scopes === 'member' && $type !== 'system') {
            $is_app = true;
            //如果是应用插件，则须确保应用节点存在
            //获取应用
            $miniapp = Miniapp::where(['dir' => $type])->find();
            if (!$miniapp) exception("插件依赖{$type}应用，请先安装{$type}应用");
            //获取应用根节点
            if (!AuthNode::where(["pid" => 0, "app_name" => $type, "type" => 'app', "scopes" => $scopes])->find()) throw new Exception("应用{$type}根节点未找到");
        }
        if ($scopes == 'all') $scopes = null; //TODO: 如果不指定all类型的角色组,则报错：all类型必须指定角色组范围
        foreach ($data as $item) {
            //参数验证
            if (!isset($item['scopes']) || !in_array($item['scopes'], ['admin', 'member'])) throw new Exception("{$item['name']}角色scopes属性有误");

            //插件角色组可同时包含 平台的角色组和租户的角色组
            $scopes = in_array($item['scopes'], ['admin', 'member']) ? $item['scopes'] : $scopes;
            if (!$scopes) exception("{$item['name']}角色scopes属性未填写，可填写admin或member");
            //是否自定义了菜单type类型，如果没有自定义菜单类型，则使用默认
            if (!isset($item['type']) || $item['type'] == "app_plugin") $item['type'] = $is_app ? "app_plugin_{$type}" : "system_plugin";
            if (!in_array($item['type'], ['system_plugin', "app_plugin_{$type}"])) exception("{$item['name']}角色type属性未定义或不规范");
            //角色记录
            $groupParam = [
                //角色名称,展示在角色列表中
                'name' => $item['name'],
                //角色唯一标识,不可重复
                'group_name' => strpos($item['group_name'], $name) !== false ? $item['group_name'] : $name . '_' . $item['group_name'],
                //角色备注
                'remark' => isset($item['remark']) ? $item['remark'] : '',
                //登陆类型 admin=总后台,member=租户后台
                'scopes' => $scopes,
                'type' => $group_type,//角色组类型：system=系统，app=应用，plugin=插件
                'app_name' => $app_name,//更改为插件名称
            ];
            //创建角色
            $group = AuthGroup::create($groupParam);
            if (!$group) throw new Exception("{$item['name']}角色导入失败");
            //TODO 2021/7/6 应用插件 不插入应用模块中
            //父节点唯一标识
            $parent_node_auth_node = isset($item['parent']) ? $item['parent'] : '';
            //插入节点
            if (isset($item['nodes']) && !empty($item['nodes'])) self::importNodeData($name, $scopes, $type, $group, $item['nodes'], $parent_node_auth_node, $item['type']);
        }

        return true;
    }

    /**
     * 导入节点
     * @param string $scopes admin/member
     * @param string $type system/bwmall
     * @param array $group 角色组
     * @param array $nodes 节点
     * @param string $parent_node_auth_node 父节点唯一标识
     * @return bool
     * @throws Exception
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    private static function importNodeData($name, $scopes, $type, $group, $nodes, $parent_node_auth_node = '', $node_type = '')
    {
        //是否是应用
        $is_app = false;
        //应用插件判断
        if ($scopes === 'member' && $type !== 'system') $is_app = true;
        //菜单节点type类型
        if (!$node_type || $node_type == 'app_plugin') $node_type = $is_app ? "app_plugin_{$type}" : "system_plugin";
        //TODO 2021/4/29 更改角色app_name为插件名称
        $app_name = $name;
        //如果传入了父节点则插入到父节点下面
        if ($parent_node_auth_node) {
            $parent_node = [
                "app_name" => $app_name,//插件名
                "type" => $node_type,//权限分类 系统插件/应用插件
                "auth_name" => $parent_node_auth_node,//权限标识,必填
                "scopes" => $scopes //scopes
            ];
            //判断父节点是否存在
            $pid = AuthNode::where($parent_node)->value('id');
            if (!$pid) throw new Exception("父节点{$parent_node_auth_node}未找到，插入节点错误");
            //废弃的原逻辑：如果不存在父节点
            //①总后台插件，父节点是188
            //②租户后台系统插件，父节点是1314
            //③排除以上两者则是应用插件，父id是应用根节点
//        } else $pid = $scopes === 'admin' ? 188 : ($type === 'system' ? 1314 : AuthNode::where(["pid" => 0, "app_name" => $type, "menu_path" => $type, "scopes" => $scopes])->value('id'));
        } else {
            //TODO 2021/4/29 非自定义父级菜单逻辑改为
            //①总后台插件，父节点为总后台插件中心
            //②租户插件，父节点为租户插件中心(系统的和应用的)
            if ($scopes == 'admin') {
                //得到总后台插件中心的id
                $pid = AuthNode::whereOr(['name' => '/manage/admin.plugin.Core/index', 'auth_name' => 'manage_admin_plugin_core_index'])->where('scopes', $scopes)->value('id');
                if (!$pid) throw new Exception("总后台插件中心节点未找到");
            } else {
                //得到租户后台插件中心节点
                $pid = AuthNode::where(['name' => '/manage/member.plugin.Core/index', 'auth_name' => 'manage_member_plugin_core_index'])->where('scopes', $scopes)->value('id');
                if (!$pid) throw new Exception("租户后台插件中心节点未找到");
            }

        }

        //找到该角色组所需的根节点 admin_system是在插件管理/plugin下 member_system是在插件管理member/addons下 member_bwmall是在应用bwmall下
        $rootNodeParam = [
            "pid" => $pid,//上级菜单id,填0代表是顶部选项卡
            "title" => (get_addons_info(self::$name))['title'],//菜单名称
            "app_name" => $app_name,//插件名
            "type" => $node_type,//权限分类 系统/应用/插件
            "menu_path" => self::$name,//根节点路径
            "name" => self::$name,//根节点路径
            "auth_name" => '',//权限标识,必填
            "param" => '',//参数
            "target" => '_self',//打开方式
            "ismenu" => 1,//是否菜单
            "icon" => '',//图标
            "remark" => '',//备注
            "scopes" => $scopes//scopes
        ];
        //插入插件根节点
        $rootNode = AuthNode::where($rootNodeParam)->find();
        if (!$rootNode) $rootNode = AuthNode::create($rootNodeParam);

        //把插件根节点导入该角色组中
        $groupNodeParam = [
            'group_id' => $group['id'],       //角色id
            'node_id' => $rootNode['id'],     //节点id
            'node_name' => $rootNode['name'], //节点名
            'auth_name' => $rootNode['auth_name'],
            'type' => $rootNode['type'],  //节点类型
        ];
        //创建角色菜单中间节点
        AuthGroupNode::create($groupNodeParam);
        //执行节点导入
        self::importNode($name, $scopes, $type, $group, $nodes, $rootNode['id'], $node_type);

        return true;
    }

    /**
     * 导入节点与角色节点中间表
     * @param array $groups
     * @param array $data
     * @param int $pid 1172是应用管理的节点id
     * @return bool
     * @throws Exception
     */
    private static function importNode($name, $scopes, $type, $group, $nodes, $pid, $node_type)
    {
        //TODO 2021/4/29 更改角色app_name为插件名称
        $app_name = $name;
        //TODO 2021/7/2 jyk 修复菜单插入顺序错误： 本级菜单倒序插入，因为是按照id大到小排列，所以最靠前的菜单要最后插入
        $nodes = array_reverse($nodes);
        foreach ($nodes as $item) {
            //插入节点
            $nodeParam = [
                "pid" => $pid,//上级菜单id,填0代表是顶部选项卡
                "title" => $item['title'],//菜单名称
                "app_name" => $app_name,//应用/插件名
                "type" => $node_type,//系统/应用
                "menu_path" => $item['menu_path'],//后台url
                "name" => $item['name'],//后台url
                "auth_name" => isset($item['auth_name']) ? $item['auth_name'] : '',//权限标识,必填
                "param" => isset($item['param']) ? $item['param'] : '',//参数
                "target" => isset($item['target']) ? $item['target'] : '_self',//打开方式
                "ismenu" => isset($item['ismenu']) ? $item['ismenu'] : 0,//是否菜单
                "icon" => isset($item['icon']) ? $item['icon'] : '',//图标
                "remark" => isset($item['remark']) ? $item['remark'] : '',//备注
                "sort" => isset($item['sort']) ? $item['sort'] : 0,//TODO 2021/7/2 jyk 插件菜单支持配置排序属性
                "scopes" => $scopes//scopes
            ];
            //节点插入
            $node = AuthNode::create($nodeParam);
            if (!$node) throw new Exception("{$item['title']}节点插入失败");

            //插入节点中间表
            $groupNodeParam = [
                'group_id' => $group['id'],
                'node_id' => $node['id'],
                'node_name' => $node['name'],
                'auth_name' => $node['auth_name'],
                'type' => $node['type'],
            ];
            AuthGroupNode::create($groupNodeParam);

            if (isset($item['children']) && !empty($item['children'])) self::importNode($name, $scopes, $type, $group, $item['children'], $node['id'], $node_type);
        }
        return true;
    }

    /**
     * 删除菜单
     * @return bool
     */
    private static function deleteMenu(): bool
    {
        //得到插件信息
        $info = get_addons_info(self::$name);
        //得到范围和类型
        list($scopes, $type) = explode('_', $info['type']);
        //TODO 2021/4/29 变更插件角色类型为plugin,app_name为插件标识
        $addon_type = 'plugin';        //角色类型
        $addon_app_name = self::$name;//角色插件标识
        //检查是否有menu.php
        $path = root_path() . "addons/" . self::$name . "/install/menu.php";
        if (!file_exists($path)) return true; //没有菜单
        $data = include $path;
        if (empty($data)) return true;  //没有菜单
        $group_name_arr = [];//用户组唯一标识
        //得到插件全部角色标识
        foreach ($data as $item) {
            $group_name_arr[] = strpos($item['group_name'], self::$name) !== false ? $item['group_name'] : self::$name . '_' . $item['group_name'];
        }
        //根据角色标识得到插件拥有的角色id
        $group_ids_arr = AuthGroup::where('group_name', 'IN', $group_name_arr)->where('type', $addon_type)->where('app_name', $addon_app_name)->column('id');
        //根据角色id得到插件拥有的节点id
        $node_ids_arr = AuthGroupNode::where('group_id', 'IN', $group_ids_arr)->column('node_id');
        //删除插件角色
        AuthGroup::where('id', 'IN', $group_ids_arr)->delete();
        //删除插件节点
        AuthNode::where('id', 'IN', $node_ids_arr)->delete();
        //删除插件角色节点中间表
        AuthGroupNode::where('group_id', 'IN', $group_ids_arr)->delete();
        //删除auth_group_access中记录
        AuthGroupAccess::where('group_id', 'IN', $group_ids_arr)->delete();
        //TODO 如果是应用插件则删除对应的应用模块[待考虑扩展]
        if ($scopes === 'member' && $type !== 'system') MiniappModule::where('group_id', 'IN', $group_ids_arr)->delete();

        return true;
    }

    /**
     * 启动菜单
     */
    private static function enableMenu()
    {
        $info = get_addons_info(self::$name);
        list($scopes, $type) = explode('_', $info['type']);

        //检查是否有menu.php
        $path = root_path() . "addons/" . self::$name . "/install/menu.php";
        if (!file_exists($path)) return true;
        $data = include $path;
        if (empty($data)) return true;
        $group_name_arr = [];//用户组唯一标识
        foreach ($data as $item) {
            $group_name_arr[] = strpos($item['group_name'], self::$name) !== false ? $item['group_name'] : self::$name . '_' . $item['group_name'];
        }

        //角色组设置为启用
        //AuthGroup::where('group_name', 'IN', $group_name_arr)->where('type', $type === 'system' ? 'system' : 'app')->where('app_name', $type === 'system' ? 'manage' : $type)->update(['status' => 1]);
        AuthGroup::where('group_name', 'IN', $group_name_arr)->update(['status' => 1]);

        return true;
    }

    /**
     * 禁用菜单
     */
    private static function disableMenu()
    {
        $info = get_addons_info(self::$name);
        list($scopes, $type) = explode('_', $info['type']);

        //检查是否有menu.php
        $path = root_path() . "addons/" . self::$name . "/install/menu.php";
        if (!file_exists($path)) return true;
        $data = include $path;
        if (empty($data)) return true;
        $group_name_arr = [];//用户组唯一标识
        foreach ($data as $item) {
            $group_name_arr[] = strpos($item['group_name'], self::$name) !== false ? $item['group_name'] : self::$name . '_' . $item['group_name'];
        }

        //角色组设置为禁用
        //AuthGroup::where('group_name', 'IN', $group_name_arr)->where('type', $type === 'system' ? 'system' : 'app')->where('app_name', $type === 'system' ? 'manage' : $type)->update(['status' => 0]);
        AuthGroup::where('group_name', 'IN', $group_name_arr)->update(['status' => 0]);


        return true;
    }


    /**安装插件的配置和组合数据(修正分类id)
     * @param $name
     */
    public static function installConfigAndGroupData($plugin_name, array $info, $trans = false)
    {
        list($scopes, $type) = explode('_', $info['type']);
        if ($scopes === 'member' && $type !== 'system') $app_name = $type; else  $app_name = '';
        //得到插件的
        if ($trans) {
            Db::startTrans();
        }
        try {
            //如果存在插件应用配置则更新插件应用配置和组合数据
            if ($app_name) {
                try {
                    //查询所有的应用插件配置并更新配置id
                    $config_tab_ids = ConfigTab::valiWhere('', null, $app_name, false)->where('plugin_name', '=', $plugin_name)->where('dir', $app_name)->where('scopes', 'member')->column('id', 'tab_name');
                } catch (\Exception $e) {
                    //TODO  JYK 20210701 修复：如果该应用插件未使用任何应用配置也没有创建应用配置表不能算报错
                    $config_tab_ids = [];
                }
                //遍历配置分类，给租户插入配置数据
                foreach ($config_tab_ids as $tab_name => $tab_id) {
                    //修正分类id参数
                    Config::valiWhere('', null, $app_name, false)->where('tab_name', '=', $tab_name)->where('dir', $app_name)->where('plugin_name', '=', $plugin_name)->update(['tab_id' => $tab_id]);
                }
                try {
                    $group_ids = ConfigGroup::valiWhere('', null, $app_name)->where('plugin_name', '=', $plugin_name)->where('dir', $app_name)->where('scopes', 'member')->column('id', 'config_name');
                } catch (\Exception $e) {
                    //TODO  JYK 20210701 修复：如果该应用插件未使用任何应用配置也没有创建应用配置表不能算报错
                    $group_ids = [];
                }
                //遍历数据组，给租户插入组合数据
                foreach ($group_ids as $config_name => $group_id) {
                    //修正数据组id参数
                    ConfigGroupData::valiWhere('', null, $app_name)->where('config_name', '=', $config_name)->where('plugin_name', '=', $plugin_name)->where('dir', $app_name)->update(['group_id' => $group_id]);
                }
            }
            //如果存在系统配置和系统租户配置，更新对应分类id
            //查询所有的应用插件配置并更新配置id
            $config_tab_ids = ConfigTab::valiWhere('', null, '', false)->where('plugin_name', '=', $plugin_name)->column('id', 'tab_name');
            //遍历配置分类，给租户插入配置数据
            foreach ($config_tab_ids as $tab_name => $tab_id) {
                //修正分类id参数
                Config::valiWhere('', null, '', false)->where('tab_name', '=', $tab_name)->where('plugin_name', '=', $plugin_name)->update(['tab_id' => $tab_id]);
            }
            $group_ids = ConfigGroup::valiWhere('', null, '')->where('plugin_name', '=', $plugin_name)->column('id', 'config_name');
            //遍历数据组，给租户插入组合数据
            foreach ($group_ids as $config_name => $group_id) {
                //修正数据组id参数
                ConfigGroupData::valiWhere('', null, '')->where('config_name', '=', $config_name)->where('plugin_name', '=', $plugin_name)->update(['group_id' => $group_id]);
            }

            if ($trans) {
                Db::commit();
            }
        } catch (\Exception $e) {
            if ($trans) {
                Db::rollback();
            }
            throw new Exception("安装配置和组合数据失败：" . $e->getMessage());
        }

        return true;

    }

    /**初始化租户配置和数据组（购买时）
     * @param $member_id
     * @param $dir
     */
    public static function initMemberConfigAndData($member_id, $plugin_name, $info, $trans = false)
    {
        list($scopes, $type) = explode('_', $info['type']);
        if ($scopes === 'member' && $type !== 'system') $app_name = $type; else  $app_name = '';
        if ($trans) {
            Db::startTrans();
        }
        $config_data = $group_data = $config_data_sys = $group_data_sys = [];
        //查看插件是否有应用配置，如果有，则插入配置
        if ($app_name) {
            //得到租户的初始化数据
            try {
                //配置列表
                $config_list = Config::valiWhere('', null, $app_name, false)->where('member_id', 0)->where('scopes', 'member')->where('dir', '=', $app_name)->where('plugin_name', '=', $plugin_name)->select()->toArray();        //组装提交数据
            } catch (\Exception $e) {
                //TODO  JYK 20210701 修复：如果该应用插件未使用任何应用配置也没有创建应用配置表不能算报错
                $config_list = [];
            }

            //得到租户的初始化数据
            try {
                //组合数据
                $config_data_list = ConfigGroupData::valiWhere('', null, $app_name)->where('member_id', 0)->where('scopes', 'member')->where('dir', '=', $app_name)->where('plugin_name', '=', $plugin_name)->select()->toArray();
            } catch (\Exception $e) {
                //TODO  JYK 20210701 修复：如果该应用插件未使用任何应用配置也没有创建应用配置表不能算报错
                $config_data_list = [];
            }


            //组装提交数据
            //租户配置
            foreach ($config_list as $config) {
                $member_config = Config::valiWhere('', null, $app_name, false)->where('member_id', $member_id)->where('scopes', 'member')->where('dir', '=', $app_name)->where('plugin_name', '=', $plugin_name)->where('config_name', $config['config_name'])->find();
                if (!$member_config) {
                    unset($config['id']);
                    $config['member_id'] = $member_id;
                    $config_data[] = $config;
                }
            }
            //租户组合数据
            foreach ($config_data_list as $data) {
                $member_data = ConfigGroupData::valiWhere('', null, $app_name)->where('member_id', $member_id)->where('scopes', 'member')->where('dir', '=', $app_name)->where('plugin_name', '=', $plugin_name)->where('config_name', $data['config_name'])->find();
                if (!$member_data) {
                    unset($data['id']);
                    $data['member_id'] = $member_id;
                    $group_data[] = $data;
                }
            }


        }
        //查看插件是否有租户系统配置，如果有，则插入
        //得到租户的初始化数据
        //配置列表
        $config_list = Config::valiWhere('', null, '', false)->where('member_id', 0)->where('scopes', 'member')->where('plugin_name', '=', $plugin_name)->select()->toArray();        //组装提交数据
        //组合数据
        $config_data_list = ConfigGroupData::valiWhere('', null, '')->where('member_id', 0)->where('scopes', 'member')->where('plugin_name', '=', $plugin_name)->select()->toArray();
        //组装提交数据
        //租户配置
        foreach ($config_list as $config) {
            $member_config = Config::valiWhere('', null, '', false)->where('member_id', $member_id)->where('scopes', 'member')->where('plugin_name', '=', $plugin_name)->where('config_name', $config['config_name'])->find();
            if (!$member_config) {
                unset($config['id']);
                $config['member_id'] = $member_id;
                $config_data_sys[] = $config;
            }
        }
        //租户组合数据
        foreach ($config_data_list as $data) {
            $member_data = ConfigGroupData::valiWhere('', null, '')->where('member_id', $member_id)->where('scopes', 'member')->where('plugin_name', '=', $plugin_name)->where('config_name', $data['config_name'])->find();
            if (!$member_data) {
                unset($data['id']);
                $data['member_id'] = $member_id;
                $group_data_sys[] = $data;
            }
        }

        try {
            //执行提交
            if ($config_data) { //应用配置
                $config = Config::valiWhere('', null, $app_name, false);
                $config->saveAll($config_data);
            }
            if ($group_data) { //应用组合数据
                $data = ConfigGroupData::valiWhere('', null, $app_name);
                $data->saveAll($group_data);
            }
            if ($config_data_sys) { //系统配置
                $config = Config::valiWhere('', null, '', false);
                $config->saveAll($config_data_sys);
            }
            if ($group_data_sys) { //系统组合数据
                $data = ConfigGroupData::valiWhere('', null, '');
                $data->saveAll($group_data_sys);
            }
            if ($trans) {
                Db::commit();
            }
        } catch (\Exception $e) {
            if ($trans) {
                Db::rollback();
            }
            throw new Exception("安装租户配置和组合数据失败：" . $e->getMessage());
        }

    }


    /**
     * 导出插件安装的配置和数据组文件
     */
    public static function exportPluginConfig($plugin_name, $dir_path, $un_dir_path)
    {
        //插件信息
        $info = Plugin::where('name', $plugin_name)->find();
        if (!$info) return true;
        list($scopes, $type) = explode('_', $info['type']);
        if ($scopes === 'member' && $type !== 'system') $app_name = $type; else  $app_name = '';
        $config_tab_sql = '';//配置分类安装sql
        $config_sql = '';  //配置安装sql
        $group_sql = ''; //数据组安装sql
        $group_data_sql = ''; //数据组数据安装sql
        $install_sql = '';

        $config_tab_delete_sql = ''; //配置分类卸载sql
        $config_delete_sql = '';  //配置卸载sql
        $group_delete_sql = '';  //数据组卸载sql
        $group_data_delete_sql = '';  //数据组数据卸载sql
        $uninstall_sql = '';
        $tableSql = MiniappService::getInstallSql($plugin_name); //得到所有的插件安装表
        if ($app_name) {
            //查询应用配置
            //查询应用配置分类
            $configTabs = ConfigTab::valiWhere('', null, $app_name, false)->where('dir', $app_name)->where('scopes', 'member')->where('plugin_name', $plugin_name)->column('tab_name');
            $configTabList = ConfigTab::valiWhere('', null, $app_name, false)->where('dir', $app_name)->where('scopes', 'member')->where('plugin_name', $plugin_name)->select()->toArray();
            if ($configTabs) {
                foreach ($configTabList as $tab) {
                    unset($tab['id']);
                    //得到配置分类插入sql
                    $insertSQL = ConfigTab::valiWhere('', null, $app_name, false)->fetchSql(true)->save($tab);
                    $config_tab_sql .= $insertSQL . ';' . PHP_EOL;
                    //得到配置分类卸载sql
                    $deleteSQL = ConfigTab::valiWhere('', null, $app_name, false)->fetchSql(true)->where('tab_name', $tab['tab_name'])->where('plugin_name', $plugin_name)->delete();
                    $config_tab_delete_sql .= $deleteSQL . ';' . PHP_EOL;
                    //得到配置卸载sql
                    $deleteSQL = Config::valiWhere('', null, $app_name, false)->fetchSql(true)->where('tab_name', $tab['tab_name'])->where('plugin_name', $plugin_name)->delete();
                    $config_delete_sql .= $deleteSQL . ';' . PHP_EOL;
                }
                //查询所有初始化配置
                $configs = Config::valiWhere('', null, $app_name, false)->where('dir', $app_name)->where('member_id', 0)->where('scopes', 'member')->where('tab_name', 'in', $configTabs)->where('plugin_name', $plugin_name)->select()->toArray();
                foreach ($configs as $config) {
                    //得到配置值插入sql
                    unset($config['id']);
                    $insertSQL = Config::valiWhere('', null, $app_name, false)->fetchSql(true)->save($config);
                    $config_sql .= $insertSQL . ';' . PHP_EOL;
                }
            }

            //生成bwdata
            $configGroups = ConfigGroup::valiWhere('', null, $app_name)->where('dir', $app_name)->where('plugin_name', $plugin_name)->where('scopes', 'member')->column('config_name');
            $configGroupList = ConfigGroup::valiWhere('', null, $app_name)->where('dir', $app_name)->where('plugin_name', $plugin_name)->where('scopes', 'member')->select()->toArray();
            foreach ($configGroupList as $configGroup) {
                //得到数据组插入sql
                unset($configGroup['id']);
                $insertSQL = ConfigGroup::valiWhere('', null, $app_name)->fetchSql(true)->save($configGroup);
                $group_sql .= $insertSQL . ';' . PHP_EOL;
                //得到数据组卸载sql
                $deleteSQL = ConfigGroup::valiWhere('', null, $app_name)->fetchSql(true)->where('config_name', $configGroup['config_name'])->where('plugin_name', $plugin_name)->delete();
                $group_delete_sql .= $deleteSQL . ';' . PHP_EOL;

                //得到数据组数据卸载sql
                $deleteSQL = ConfigGroupData::valiWhere('', null, $app_name)->fetchSql(true)->where('config_name', $configGroup['config_name'])->where('plugin_name', $plugin_name)->delete();
                $group_data_delete_sql .= $deleteSQL . ';' . PHP_EOL;
            }
            //查询所有组合数据的初始化数据
            $configGroupDatas = ConfigGroupData::valiWhere('', null, $app_name)->where('dir', $app_name)->where('member_id', 0)->where('scopes', 'member')->where('config_name', 'in', $configGroups)->where('plugin_name', $plugin_name)->select()->toArray();
            foreach ($configGroupDatas as $configGroupData) {
                //得到数据组插入sql
                unset($configGroupData['id']);
                $insertSQL = ConfigGroupData::valiWhere('', null, $app_name)->fetchSql(true)->save($configGroupData);
                $group_data_sql .= $insertSQL . ';' . PHP_EOL;

            }
        }
        //查询系统配置
        //查询系统配置分类
        $configTabs = ConfigTab::valiWhere('', null, '', false)->where('plugin_name', $plugin_name)->column('tab_name');
        $configTabList = ConfigTab::valiWhere('', null, '', false)->where('plugin_name', $plugin_name)->select()->toArray();
        if ($configTabs) {
            foreach ($configTabList as $tab) {
                unset($tab['id']);
                //得到配置分类插入sql
                $insertSQL = ConfigTab::valiWhere('', null, '', false)->fetchSql(true)->save($tab);
                $config_tab_sql .= $insertSQL . ';' . PHP_EOL;
                //得到配置分类卸载sql
                $deleteSQL = ConfigTab::valiWhere('', null, '', false)->fetchSql(true)->where('tab_name', $tab['tab_name'])->where('plugin_name', $plugin_name)->delete();
                $config_tab_delete_sql .= $deleteSQL . ';' . PHP_EOL;
                //得到配置卸载sql
                $deleteSQL = Config::valiWhere('', null, '', false)->fetchSql(true)->where('tab_name', $tab['tab_name'])->where('plugin_name', $plugin_name)->delete();
                $config_delete_sql .= $deleteSQL . ';' . PHP_EOL;
            }
            //查询所有初始化配置
            $configs = Config::valiWhere('', null, '', false)->where('member_id', 0)->where('tab_name', 'in', $configTabs)->where('plugin_name', $plugin_name)->select()->toArray();
            foreach ($configs as $config) {
                //得到配置值插入sql
                unset($config['id']);
                $insertSQL = Config::valiWhere('', null, '', false)->fetchSql(true)->save($config);
                $config_sql .= $insertSQL . ';' . PHP_EOL;
            }
        }

        //生成bwdata
        $configGroups = ConfigGroup::valiWhere('', null, '')->where('plugin_name', $plugin_name)->column('config_name');
        $configGroupList = ConfigGroup::valiWhere('', null, '')->where('plugin_name', $plugin_name)->select()->toArray();
        foreach ($configGroupList as $configGroup) {
            //得到数据组插入sql
            unset($configGroup['id']);
            $insertSQL = ConfigGroup::valiWhere('', null, '')->fetchSql(true)->save($configGroup);
            $group_sql .= $insertSQL . ';' . PHP_EOL;

            //得到数据组卸载sql
            $deleteSQL = ConfigGroup::valiWhere('', null, '')->fetchSql(true)->where('config_name', $configGroup['config_name'])->where('plugin_name', $plugin_name)->delete();
            $group_delete_sql .= $deleteSQL . ';' . PHP_EOL;

            //得到数据组数据卸载sql
            $deleteSQL = ConfigGroupData::valiWhere('', null, '')->fetchSql(true)->where('config_name', $configGroup['config_name'])->where('plugin_name', $plugin_name)->delete();
            $group_data_delete_sql .= $deleteSQL . ';' . PHP_EOL;
        }
        //查询所有组合数据的初始化数据
        $configGroupDatas = ConfigGroupData::valiWhere('', null, '')->where('member_id', 0)->where('config_name', 'in', $configGroups)->where('plugin_name', $plugin_name)->select()->toArray();
        foreach ($configGroupDatas as $configGroupData) {
            //得到数据组插入sql
            unset($configGroupData['id']);
            $insertSQL = ConfigGroupData::valiWhere('', null, '')->fetchSql(true)->save($configGroupData);
            $group_data_sql .= $insertSQL . ';' . PHP_EOL;

        }
        //修改表前缀
        $table_prefix = config('database.connections.mysql.prefix');
        //组装表安装sql
        if ($tableSql['install']) $install_sql .= PHP_EOL . $tableSql['install'] . PHP_EOL;
        //组装配置的安装sql
        if ($config_tab_sql) $install_sql .= PHP_EOL . $config_tab_sql . PHP_EOL;
        if ($config_sql) $install_sql .= PHP_EOL . $config_sql . PHP_EOL;
        if ($group_sql) $install_sql .= PHP_EOL . $group_sql . PHP_EOL;
        if ($group_data_sql) $install_sql .= PHP_EOL . $group_data_sql . PHP_EOL;
        //生成配置插入语句
        if ($install_sql) file_put_contents($dir_path . 'install.sql', str_replace($table_prefix, '__BWPREFIX__', $install_sql));

        //组装表卸载sql
        if ($tableSql['uninstall']) $uninstall_sql .= PHP_EOL . $tableSql['uninstall'] . PHP_EOL;
        //组装配置的卸载sql
        if ($config_tab_delete_sql) $uninstall_sql .= PHP_EOL . $config_tab_delete_sql . PHP_EOL;
        if ($config_delete_sql) $uninstall_sql .= PHP_EOL . $config_delete_sql . PHP_EOL;
        if ($group_delete_sql) $uninstall_sql .= PHP_EOL . $group_delete_sql . PHP_EOL;
        if ($group_data_delete_sql) $uninstall_sql .= PHP_EOL . $group_data_delete_sql . PHP_EOL;
        //生成配置卸载sql
        if ($uninstall_sql) file_put_contents($un_dir_path . 'uninstall.sql', str_replace($table_prefix, '__BWPREFIX__', $uninstall_sql));

    }

    /**导出安装目录
     * @param $dir
     * @param string $target_dir
     */
    public static function exportPath($dir)
    {
        $dir_path = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'install' . DS;
        $un_dir_path = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'uninstall' . DS;
        $target_dir = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'install' . DS . 'static' . DS;
        return [
            'static' => $target_dir . '*',
            'group' => $dir_path . 'menu.php',
            'event' => $dir_path . 'listen.php',
            'install' => $dir_path . 'install.sql',
            'uninstall' => $un_dir_path . 'uninstall.sql',
            'app' => root_path() . DS . $dir . '======>' . root_path() . 'public' . DS . 'addons' . DS . $dir,
            'zip' => '以上文件将被打包进：' . root_path() . 'public' . DS . 'addons' . DS . $dir . '.zip',
        ];
    }


    /**打包当前应用
     * @param $dir
     */
    public static function exportPackage($dir)
    {
        $dir_path = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'install';
        @mkdir($dir_path, 0777, true);
        $dir_path .= DS;

        $un_dir_path = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'uninstall';
        @mkdir($un_dir_path, 0777, true);
        $un_dir_path .= DS;

        $sorce_dir = root_path() . 'public' . DS . 'static' . DS . 'addons' . DS . strtolower($dir) . DS;
        $target_dir = root_path() . 'public' . DS . 'addons' . DS . $dir . DS . 'install' . DS . 'static' . DS;
        $sorce_app = root_path() . 'addons' . DS . $dir . DS;
        $target_app = root_path() . 'public' . DS . 'addons' . DS . $dir . DS;
        $zip_path = root_path() . 'public' . DS . 'addons' . DS . $dir . '.zip';
        $event_ini = $dir_path . 'listen.php';
        $menu = $dir_path . 'menu.php';
        MiniappService::exportMiniappStatic($sorce_app, $target_app);//迁移项目文件
        self::exportPluginConfig($dir, $dir_path, $un_dir_path);//导入配置文件
        MiniappService::exportMiniappStatic($sorce_dir, $target_dir);//导入静态文件
        MiniappService::exportMiniappEventInit($event_ini);//导入钩子配置文件
        self::exportPluginMenu($dir, $menu);//导入菜单配置
        //项目打压缩包
        File::createSimpleZip($zip_path, $target_app);
        //删除生成文件夹
        File::del_dir($target_app);

    }

    /**
     * 导出插件用户组数据至文件
     * @param string $dir 应用目录名
     */
    public static function exportPluginMenu($dir, $target_dir = 'menu.php')
    {
        //插件
        $miniapp = Plugin::where('name', $dir)->find();
        if (!$miniapp) return true;
        //查询插件所有角色（）
        $miniapp_modules = AuthGroup::alias('auth_group')
            ->field('auth_group.id, auth_group.name, auth_group.group_name, auth_group.remark, auth_group.type,auth_group.scopes')
            ->where('auth_group.app_name', $dir)
            ->where('auth_group.type', 'plugin')
            ->order(['auth_group.id' => 'asc'])
            ->select()->toArray();
        if (!$miniapp_modules) return true;//不存在角色
        //总后台插件中心根节点
        $admin_root_node = AuthNode::where('menu_path', '/manage/admin/plugin/core/index')->where('type', 'system')->where('scopes', 'admin')->field('id, pid, title')->find();
        //租户插件中心根节点
        $member_root_node = AuthNode::where('menu_path', '/manage/member/plugin/core/index')->where('type', 'system')->where('scopes', 'member')->field('id, pid, title')->find();
        if (!$admin_root_node) return true;
        if (!$member_root_node) return true;
        //插入角色节点
        foreach ($miniapp_modules as &$miniapp_module) {
            $nodes = AuthNode::alias('node')
                ->join('auth_group_node', 'node.id = auth_group_node.node_id')
                ->where([
                    ['auth_group_node.group_id', '=', $miniapp_module['id']],
                ])
                ->field('node.id, node.pid, node.title, node.menu_path, node.name, node.auth_name, node.ismenu, node.param, node.target, node.icon, node.remark, node.type')
                ->order('node.sort desc, node.id asc')
                ->select()->toArray();
            if (!$nodes) continue;
            $miniapp_module['type'] = $nodes[0]['type'];
            if ($miniapp_module['scopes'] == 'admin') {
                $nodes = deal_list_to_tree($nodes, 'id', 'pid', 'children', false, $admin_root_node['id']);
            } else {
                $nodes = deal_list_to_tree($nodes, 'id', 'pid', 'children', false, $member_root_node['id']);
            }
            $miniapp_module['nodes'] = $nodes ? $nodes[0]['children'] : $nodes;
            unset($miniapp_module['id']);
        }


        file_put_contents($target_dir, '<?php' . "\n\nreturn " . var_export($miniapp_modules, true) . ";");
        self::filterMenu($target_dir);
        return true;
    }


    /**
     * 菜单生成文件正则过滤
     * @param string $name 插件名称
     * @return  boolean
     */
    public static function filterMenu($file_path)
    {
        if (is_file($file_path)) {
            $lines = file($file_path);
            $templine = '';
            foreach ($lines as $line) {
                if (strpos($line, "'id'") !== false || strpos($line, "'pid'") !== false) continue;
                $templine .= $line;
            }
        }
        file_put_contents($file_path, $templine);
        return true;
    }


    /**
     * 插件安装检测
     * @param $dir
     * @return bool
     * @throws \think\db\exception\DataNotFoundException
     * @throws \think\db\exception\DbException
     * @throws \think\db\exception\ModelNotFoundException
     */
    public static function checkInstall($dir): bool
    {
        $plugin = Plugin::where('name', $dir)->find();
        //未安装
        if (!$plugin) return false;
        //未安装或已禁用
        if ($plugin['status'] != '1') return false;
        return true;
    }

    /**
     * 调用插件事件
     * @param $event //事件数据
     * @param $event_name //事件名
     * @return array|mixed
     */
    public static function handle($event, $event_name)
    {
        list($user_id, $plugin) = $event;
        //查询是否有应用事件
        $Event = self::getEventData($plugin['name']);
        if (!empty($Event)) {
            $list = $Event;
            foreach ($list as $event_item) {
                $class = null;
                $method = null;
                $scope = 'common';
                if (isset($event_item[$event_name]['class']) && $event_item[$event_name]['class']) $class = $event_item[$event_name]['class'];
                if (isset($event_item[$event_name]['method']) && $event_item[$event_name]['method']) $method = $event_item[$event_name]['method'];
                if (isset($event_item[$event_name]['scope']) && $event_item[$event_name]['scope'] && in_array($event_item[$event_name]['scope'], ['static', 'object', 'common'])) $scope = $event_item[$event_name]['scope'];
                if ($method) {
                    //方法调用
                    switch ($scope) {
                        case "common":  //公共函数
                            $method($event);
                            break;
                        case "static":  //类静态函数
                            if ($class) {
                                $class::$method($event);
                            }
                            break;
                        case "object":  //类对象函数
                            if ($class) {
                                (new $class)->$method($event);
                            }
                            break;
                    }
                }
            }
        }

    }

    /**
     * 获取插件事件配置信息
     * @param string $dir
     * @return array|mixed
     */
    public static function getEventData(string $dir)
    {
        $addon_dir = 'addons';
        //批量搜索全部事件配置
        $addon_events = root_path() . "{$addon_dir}/{$dir}/install/listen.php";
        $config = [];
        if (is_file($addon_events)) {
            $config[] = include $addon_events;
        }
        return $config;
    }
}